Fork me on GitHub

Redis cluster集群介绍

Redis Cluster 是为了解决什么问题?

虽然Redis的单机性能已经足够高了,但是面对互联网企业动辄几百G 的缓存数据,此时性能再好的单机也无法满足业务需求了。在这种情况下 Redis官方推出了集群模式:Redis ClusterRedis Cluster管理的是一组Redis,每个Redis称为一个节点。由于可以在集群中任意扩展节点,所以Cluster主要是提高了缓存系统扩展性,当然还提升了性能。我们知道,衡量一个系统有很多个指标,主要是包括性能,可用性,可扩展性,从这些方面,我们可以总结一下 :

  1. Redis 主从复制,提升的是读写性能以及数据的安全性(备份);
  2. Redis Sentinel哨兵,监控所有节点的状况,提升的是可用性;
  3. Redis Cluster集群,提升的是可扩展性,以及性能;

Redis Cluster 介绍

Redis集群通过分片的方式来保存数据库中的键值对,集群的整个数据库被分成16384个 槽(slot),然后把所有的键值对均匀的分散到这些槽位中,每个节点可以存储不同的槽数。 简单的经典架构如下图:

上图展示的是一种主从复制模型,包含三个master,每个master有一个slave,每个master分得不同的槽位。一个槽位(坑位)可以存放多个键值对,当2号master节点挂掉的时候,整个集群就会以为缺少5462-10922这个范围的槽而不可用,而此时集群会选举slave作为主节点,整个集群便不会因为槽找不到而不可用了。但是当主从节点都挂掉的时候,此时集群是不可用的。

数据一致性

Redis 并不能保证数据的强一致性,所谓强一致性,是指主从节点在任何时候都是一样的,Redis 无法保证这一点。考虑以下两种情况:
1.主从节点复制延迟:
主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。 注意:Redis 集群可能会在将来提供同步写的方法。
2.出现网络分区:
举个例子 假设集群包含 A 、 B 、 C 、 A1 、 B1 、 C1 六个节点, 其中 A 、B 、C 为主节点, A1 、B1 、C1 为A,B,C的从节点, 还有一个客户端 Z1 假设集群中发生网络分区,那么集群可能会分为两方,大部分的一方包含节点 A 、C 、A1 、B1 和 C1 ,小部分的一方则包含节点 B 和客户端 Z1 .

Z1仍然能够向主节点B中写入, 如果网络分区发生时间较短,那么集群将会继续正常运作,如果分区的时间足够让大部分的一方将B1选举为新的master,那么Z1写入B中得数据便丢失了.

注意, 在网络分裂出现期间, 客户端 Z1 可以向主节点 B 发送写命令的最大时间是有限制的, 这一时间限制称为节点超时时间(node timeout), 是 Redis 集群的一个重要的配置选项:

搭建集群环境

这里我们创建包含6台机器的集群,采取三主三从的模式,每个master有一个slave,端口分配分别是6380,6381,6382,6283,6384,6385. 这里没有采用docker环境,因为官方教程上说需要docker开启host的网络连接模式,但是这种模式仅支持linux系统,因此这里采用的是直接redis-server xxx/redis-x.conf方式启动六台机器。目录结构如下

这六个redis实例启动后,还没有组建成一个集群环境,官方提供了一个redis-trib.rb脚本来搭建集群,所以需要在Mac上先安装ruby

1
brew install ruby

安装ruby的 redis 支持

1
gem install redis

上面的redis-trib.rb文件是在redis的源码src目录里,如果找不到,可以去 githubredis项目 里面去找。另外这个文件还要增加可执行权限。

1
chmod +x redis-trib.rb

然后就可以用这个脚本来构建集群了

1
./redis-trib.rb create --replicas 1 127.0.0.1:6380 127.0.0.1:6382 127.0.0.1:6384 127.0.0.1:6385  127.0.0.1:6381 127.0.0.1:6383

replicas 1表示一主一从的模式,后面跟六个实例ip和端口。

启动完成后,会显示槽分配完成

此时我们就拥有一个redis集群了!

连接集群

1
redis-cli -c -p 6380

此时显示出 key 被分配到了哪一个槽

此时包含一个从机

cluster API
可以使用redis-cli直接连接后(要加上-c参数),查看集群信息

  1. cluster info:查看集群信息
  2. cluster nodes 查看所有节点信息

所有命令列表

集群机制说明

####节点是如何加入集群的?
通过cluster meet指令,连接到集群中的任意一个节点后,比如

1
cluster meet 127.0.0.1 7001

意思是把7001加入到当前的集群中,但是7001目前是没有分配槽的。
这个命令是如何实现的呢?大致过程是这样,比如 A和 B,B 此时想要加入加入节点,那么 A和 B 先通信,二者确认无误后,A 会通过Gossip协议把节点 B 的信息传播给其他节点,让其他节点和 B 进行通信握手,最终 B 节点就会被集群中的所有节点认识。

槽信息是如何保存的?

首先每个节点中有这样一个结构来记录槽信息:

这样判断哪个槽是否在当前节点上时,复杂度为O(1).

那如何知道哪个槽在哪个节点上?
还有一个数据结构在记录,是clusterState

这个slots就记录了每个槽对应那个节点!这样在查询时复杂度也是O(1).

不得不佩服,这样存储非常高效!

####集群模式下命令如何执行的?
当客户端发起增啥改查命令时,节点需要做以下事情。

  1. 检查键所在的槽是否在当前节点,如果是,执行执行命令。
  2. 如果槽不在当前节点,节点会向客户端返回moved错误,引导客户端转向正确的节点。

####节点数据库和单机数据库有啥区别?
集群节点对键值对的保存以及过期时间的处理完全一样。一个区别是节点只能使用0号数据库,单机可以设置多个数据库。

####重新分片如何实现的?
重新分片是指把一个节点的槽位拿一部分出来,放到新节点上,因为新加入的节没有槽位。

实现原理:

ASK 错误

考虑一种情况,要把节点 A 的部分槽位迁移到 B上,此时有一部分已经迁移到 B 上了,此时客户端开始请求,那么节点 A 会先查找是否存在本身节点上,如果不存在,会返回ASK错误,并引导客户端向 B 请求。
过程如下:

故障转移

当主节点不可用时,从节点会成为主节点。

故障检测

集群中的每个节点都会定期向集群中的其他节点发送PING信息,检测对方是否在线。如果没在规定时间内返回PONG信息,那么就会被标记为疑似下线(PFAIL).如果在一个集群中,半数以上的主节点都将某个主节点标记为疑似下线,那么这个主节点将会被标记为下线(FAIL)。

主节点下线后,故障转移开始,其他的主节点开始选举下线主节点的某个从节点作为 主节点,当从节点获取的票数>=N/2+1时,该从节点升级为主节点。选举算法基于Raft算法。

###参考
Redis 集群搭建详细指南
Redis 集群教程-官方文档
Redis集群方案应该怎么做?